#include "index.h"
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

/////////////////////////////////////DATA/////////////////////////////////////
#define BUFF_LEN 200

int num_indexers; // number of indexing threads
FILE *fp; // file pointer for files to scan

// File name buffer globals
// lock for the file names
pthread_mutex_t file_buff_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t not_empty, not_full;
int file_buff_size = 3;
int slots = 0;
char** file_buff;
int file_buff_head=0;
int file_buff_tail=0;

bool scan_done = false;

 // lock for the file names
pthread_mutex_t index_buff_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t indexed; // a file is finished being indexed
int index_buff_size = 20;
bool index_done = false; // all indexers are done
char search_file[MAXPATH]; // the file we are currently searching for
char** index_buff; // files that are indexed
int num_indexed = 0; // current length of the index_buff

pthread_mutex_t ht_mutex = PTHREAD_MUTEX_INITIALIZER;

/////////////////////////////////////CODE/////////////////////////////////////

void print_buff(){
	int i, j;
	printf("slots:%d head:%d tail:%d\n", slots, file_buff_head, file_buff_tail);
	for(i = 0, j = file_buff_tail; i < slots; i++, j = (j+1) % file_buff_size)
		 printf("%d: %s\n", j, file_buff[j]);
	printf("================\n");
}

void add_to_buffer(char* filename){

	pthread_mutex_lock(&file_buff_lock);
	while(slots == file_buff_size) {
		pthread_cond_wait(&not_full,&file_buff_lock);
	}
	file_buff[file_buff_head] = strdup(filename);
	file_buff_head = (file_buff_head + 1) % file_buff_size;
	slots++;
	//printf("IN SCAN:\n");
	//print_buff();
	pthread_cond_signal(&not_empty);
	pthread_mutex_unlock(&file_buff_lock);
}

void *scan() {
	char* line = malloc(sizeof(char) * MAXPATH);
	char* filename;
	while (fgets(line, MAXPATH, fp) != NULL) {
	
		filename = line;
		if ( filename[0] == '.')
			filename += 2;
		filename = strtok(filename, "\n");
		add_to_buffer(filename);
	}
	
	pthread_mutex_lock(&file_buff_lock);
	scan_done = true;
	pthread_cond_broadcast(&not_empty);
	pthread_mutex_unlock(&file_buff_lock);
	
	pthread_exit(NULL);
}

int get_from_buffer(char* to_remove){
	int success;
	// 0= success, 1=not
	pthread_mutex_lock(&file_buff_lock);
	//printf("before\n");
	while(scan_done == false && slots == 0) {
		//printf("waitin%ld\n", pthread_self());
		pthread_cond_wait(&not_empty, &file_buff_lock);
		//printf("waitout%ld\n", pthread_self());
	}
	//printf("after\n");
	if (slots > 0){
		strcpy(to_remove, file_buff[file_buff_tail]);
		//free(file_buff[file_buff_tail]);
		file_buff_tail = (file_buff_tail + 1) % file_buff_size;
		slots--;
		success = 0;
	}
	else 
		success = 1;

	//printf("IN INDEX:\n");
	//print_buff();
	pthread_cond_signal(&not_full);
	pthread_mutex_unlock(&file_buff_lock);
	return success;
}

void *index_file() {
	char filename[MAXPATH];
	char buffer[BUFF_LEN];
	FILE * file;
	
	//printf("in%ld\n", pthread_self());
	while(1){
		int success = get_from_buffer(filename);
		// no more files
		if (success == 1){
			//printf("no more files\n");
			break;
		}
		file = fopen(filename, "r");
		if (file == NULL){
			perror("cannot open file\n");
			continue;
		}
		
		int line_number = 1;
		
		while (fgets(buffer, BUFF_LEN, file)!= NULL) {
			char * word;
			char * saveptr;
			word = strtok_r(buffer, " \n\t-_!@#$%^&*()_+=,;./<>?", &saveptr);
			while (word != NULL) {
				//pthread_mutex_lock( &ht_mutex);
				if ( strlen(word) >= 3 )
					insert_into_index(word, filename, line_number);
				//pthread_mutex_unlock( &ht_mutex);
				word = strtok_r(NULL, " \n\t-_!@#$%^&*()_+=,;./<>?",&saveptr);
			}
			line_number = line_number + 1;
		}

		fclose(file);
		
		pthread_mutex_lock(&index_buff_lock);
		if(strcmp(filename, search_file) == 0) {
			pthread_cond_signal(&indexed);
		}
		// check to see if the buffer is big enough
		if(num_indexed >= index_buff_size) {
			index_buff = realloc(index_buff, index_buff_size * 2);
			index_buff_size *= 2;
		}
		index_buff[num_indexed++] = strdup(filename);
		pthread_mutex_unlock(&index_buff_lock);
	}
	
	pthread_exit(NULL);
}

void *search_table() {
	char userIn[130]; // buffer for user input
	char *word; // the word to search for
	char *file; // the file to search
	char *tokenNext; // pointer for parsing ' '
	bool advanced = false;
	index_search_results_t * results;
	bool file_indexed;
	
	while(fgets(userIn, 130, stdin) != NULL) {
		advanced = false;
		
		// separate by spaces
		file = strtok_r(userIn, " \n", &tokenNext);
		
		// if there is more than 2 words, use advanced search
		word = strtok_r(NULL, " \n", &tokenNext);
		if(word) {
			advanced = true;
			pthread_mutex_lock(&index_buff_lock);
			strcpy(search_file, file);
			pthread_mutex_unlock(&index_buff_lock);
		}
		else word = file;
		
		// if user just enters a new line
		if(word == NULL) continue;
		
		if(advanced) {
			int j;
			file_indexed = false;
				
			pthread_mutex_lock(&index_buff_lock);
			for(j = 0; j < num_indexed; j++) {
				if(strcmp(index_buff[j], file) == 0) file_indexed = true;
			}
			while(!file_indexed && !index_done) {
				// If not in the list, wait for the file
				pthread_cond_wait(&indexed, &index_buff_lock);

				// See if the file is in the list
				for(; j < num_indexed; j++) {
					if(strcmp(index_buff[j], file) == 0) file_indexed = true;
				}
			}
			pthread_mutex_unlock(&index_buff_lock);
		}
		
		// search
		//pthread_mutex_lock(&ht_mutex);
		results = find_in_index(word);
		//pthread_mutex_unlock(&ht_mutex);
		if (results) {
			// word is found
			int i;
			
			if(advanced && !file_indexed) {
				// word is found, but file doesn't exist
				printf("ERROR: File %s not found\n", file);
			} else {
				// word is found, and file exists
				for (i = 0; i < results->num_results; i++) {
					if(advanced && (strcmp(results->results[i].file_name,file) == 0)) {
						printf("FOUND: %s: %d\n", results->results[i].file_name,
								results->results[i].line_number);
					}
					if(!advanced) {
						printf("FOUND: %s: %d\n", results->results[i].file_name,
								results->results[i].line_number);
					}
				}
			}
			
			free(results);
		} else if(advanced && !file_indexed) {
			// the file doesn't exist, but word might exist
			printf("ERROR: File %s not found\n", file);
		} else {
			// the word doesn't exist
			printf("Word not found.\n");
		}
	}
	pthread_exit(NULL);
}

int main(int argc, char*argv[]) {
	pthread_t scanner; // scanner thread
	pthread_t search; // search thread
	int i; // iteration variable
	
	if(argc != 3){
		printf("Usage: search-engine <num-indexer-threads> <file-list>\n");
		exit(1);
	}
	
	num_indexers = atoi(argv[1]);
	pthread_t indexers[num_indexers]; // an array of indexer threads
	
	// open file list
	fp = fopen(argv[2], "r");
	if(fp == NULL) {
		// file error occurred
		fprintf(stderr,"File Error\n");
		exit(1);
	}
	
	// create filename buffer
	file_buff = (char**)malloc(sizeof(char*) * file_buff_size);
	// create indexed buffer
	index_buff = (char**)malloc(sizeof(char*) * index_buff_size);
	
	// create hash table
	init_index();
	
	// initialize condition variables
	pthread_cond_init(&not_empty, NULL);
	pthread_cond_init(&not_full, NULL);
	
	// start threads
	pthread_create(&scanner, NULL, scan, NULL);
	for(i = 0; i < num_indexers; i++) {
		pthread_create(&indexers[i], NULL, index_file, NULL);
	}
	
	pthread_create(&search, NULL, search_table, NULL);
	
	// join threads
	pthread_join(scanner, NULL);

	for(i = 0; i < num_indexers; i++) {
		pthread_join(indexers[i], NULL);
	}
	// tell the seacher that all threads are indexed
	pthread_mutex_lock(&index_buff_lock);
	index_done = true;
	pthread_cond_signal(&indexed);
	pthread_mutex_unlock(&index_buff_lock);
	
	pthread_join(search, NULL);

	// close file-name file
	fclose(fp);
	
	exit(0);
}
